/*
 * ArCoLIVE - Live Internet Videoconference Environment
 * Information Technology Departament - Federal University of Alagoas - Brazil
 *
 * Copyright (C) 2005 Leandro Melo de Sales (leandro@labpesquisas.tci.ufal.br)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package br.ufal.tci.nexos.arcolive.service.broker;

import java.lang.reflect.Constructor;
import java.net.Socket;

import br.ufal.tci.nexos.arcolive.ArCoLIVESecurityManager;
import br.ufal.tci.nexos.arcolive.ArCoLIVEServerProperties;
import br.ufal.tci.nexos.arcolive.command.ArCoLIVEClientCommand;
import br.ufal.tci.nexos.arcolive.command.ArCoLIVECommandRepository;
import br.ufal.tci.nexos.arcolive.command.ArCoLIVECommandResult;
import br.ufal.tci.nexos.arcolive.events.ParticipantConnectionEvent;
import br.ufal.tci.nexos.arcolive.exception.ArCoLIVECannotProcessCommandException;
import br.ufal.tci.nexos.arcolive.exception.ArCoLIVECommandNotFoundException;
import br.ufal.tci.nexos.arcolive.exception.ArCoLIVEException;
import br.ufal.tci.nexos.arcolive.exception.ArCoLIVEPersistenceException;
import br.ufal.tci.nexos.arcolive.logger.ArCoLIVELogRepository;
import br.ufal.tci.nexos.arcolive.participant.ArCoLIVEParticipant;
import br.ufal.tci.nexos.arcolive.participant.ArCoLIVEParticipantGroup;
import br.ufal.tci.nexos.arcolive.participant.ParticipantListener;
import br.ufal.tci.nexos.arcolive.persistence.ArCoLIVEPersistence;
import br.ufal.tci.nexos.arcolive.persistence.ArCoLIVEPersistenceConnectionPoll;
import br.ufal.tci.nexos.arcolive.persistence.GenericResultSet;
import br.ufal.tci.nexos.arcolive.protocol.ArCoLIVEConnectionProtocol;
import br.ufal.tci.nexos.arcolive.service.connection.ConnectionRequest;
import br.ufal.tci.nexos.arcolive.service.connection.NewConnectionListenerIF;
import br.ufal.tci.nexos.arcolive.service.connection.command.SendDatabaseErrorMessageCommand;
import br.ufal.tci.nexos.arcolive.service.connection.command.SendDeniedPermissionCommandCommand;
import br.ufal.tci.nexos.arcolive.service.connection.command.SendInvalidCommandCommand;
import br.ufal.tci.nexos.arcolive.util.CommandValidator;

/**
 * CLASSNAME.java
 * 
 * CLASS DESCRIPTION
 * 
 * @see CLASSNAME
 * 
 * @author <a href="mailto:felipe@labpesquisas.tci.ufal.br">Felipe Barros Pontes</a>.
 * @author <a href="mailto:leandro@labpesquisas.tci.ufal.br">Leandro Melo de
 *         Sales</a>.
 * @since 0.1
 * @version 0.1
 * 
 * <p>
 * <b>Revisions:</b>
 * 
 * <p>
 * <b>yyyymmdd USERNAME:</b>
 * <ul>
 * <li> VERSION
 * </ul>
 */
public class ArCoLIVEServiceBroker implements NewConnectionListenerIF,
		ParticipantListener {

	private ArCoLIVEParticipantGroup participantGroup;

	private static ArCoLIVEServiceBroker singleton = null;

	ArCoLIVEPersistenceConnectionPoll pcp = null;

	private ArCoLIVEServiceBroker() {
		this.participantGroup = new ArCoLIVEParticipantGroup(
				ArCoLIVEServerProperties.getInstance().getIntProperty(
						"connection.maxInstances"));
		try {
			this.pcp = ArCoLIVEPersistenceConnectionPoll.getInstance();
		} catch (ArCoLIVEPersistenceException e) {
			System.exit(0);
			ArCoLIVELogRepository.getInstance().log(e.getMessage());
		}
	}

	public static ArCoLIVEServiceBroker getInstance() {
		if (singleton == null) {
			singleton = new ArCoLIVEServiceBroker();
		}
		return singleton;
	}

	public void newConnectionRequest(Socket socket) {
		ArCoLIVEParticipant participant = null;
		ArCoLIVEConnectionProtocol connectionProtocol = new ArCoLIVEConnectionProtocol();

		try {
			int protocolId = ArCoLIVEConnectionProtocol.ID;
			participant = new ArCoLIVEParticipant(socket);
			ConnectionRequest request = new ConnectionRequest(participant);
			request.addParameter("commandId", Integer
					.toString(ArCoLIVEConnectionProtocol.CONNECTED));
			ArCoLIVECommandResult commandResult = connectionProtocol
					.handleRequest(request);
			String response = protocolId + "#response="
					+ commandResult.getResult();
			participant.getConnectionService().getConnection().sendMessage(
					response);
			ArCoLIVELogRepository.getInstance().log(
					"New Participant Connection " + participant);

		} catch (ArCoLIVEException e) {
			ArCoLIVELogRepository.getInstance().log(
					"Error while trying to handle participat connection");
		}

	}

	private ArCoLIVECommandResult executeDynamicCommand(
			ArCoLIVEPersistence persistence, ConnectionRequest request)
			throws ArCoLIVECannotProcessCommandException,
			ArCoLIVECommandNotFoundException {
		
		ArCoLIVEClientCommand command = null;
		ArCoLIVECommandResult commandResult = null;
		ArCoLIVEParticipant participant = (ArCoLIVEParticipant) request
				.getOwner();
		int commandId = request.getIntegerParameter("commandId").intValue();
		GenericResultSet result = persistence.getParametersCommand(commandId);

		if (CommandValidator.validate(request, result)) {
			int requestId = request.getIntegerParameter("requestId").intValue();
			String response = request.getProtocolId() + "#requestId="
					+ requestId;

			result = persistence.getOneCommand(commandId);
			String name = result.getValue("name");
			String classpath = result.getValue("implementationClasspath");
			ArCoLIVECommandRepository commandRepository = ArCoLIVECommandRepository
					.getInstance();
			if (commandRepository.isCommandStored(name)) {
				command = commandRepository.getCommand(name);
			} else {
				try {
					Class commandClass = Class.forName(classpath);
					Constructor commandClassConstructor = commandClass
							.getConstructor(new Class[] { ConnectionRequest.class });
					command = (ArCoLIVEClientCommand) commandClassConstructor
							.newInstance(new Object[] { request });
					commandRepository.addCommand(name, command);
				} catch (Exception e) {
					throw new ArCoLIVECommandNotFoundException(
							"Command not found or command do not implement the required constructor (commandId "
									+ commandId + ")");
				}
			}

			commandResult = command.execute();
			response += "#response=" + commandResult.getResult();
			Exception cause = commandResult.getCause();
			if (cause != null) {
				response += "#cause=" + cause.getMessage();
				ArCoLIVELogRepository.getInstance().log(
						"Erro on command execution from participant "
								+ participant + ": " + cause.getMessage());
			}
			participant.getConnectionService().getConnection().sendMessage(
					response);

		} else {
			throw new ArCoLIVECannotProcessCommandException("Invalid comand");
		}

		return commandResult;
	}

	public void participantEvent(ParticipantConnectionEvent event) {
		ArCoLIVEParticipant participant = (ArCoLIVEParticipant) event
				.getSource();
		ConnectionRequest request = null;
		ArCoLIVEPersistence persistence = null;

		try {
			ArCoLIVESecurityManager securityManager = ArCoLIVESecurityManager
					.getInstance();
			persistence = this.pcp.getDatabaseConnection();
			request = new ConnectionRequest(event);

			ArCoLIVELogRepository.getInstance().log(
					"The request will be Handled by protocol id "
							+ request.getProtocolId());

			int commandId = request.getIntegerParameter("commandId").intValue(); // always
			// we
			// need
			// the
			// commandId
			// in
			// the
			// rawcommand
			int serviceId = -1;
			int instanceId = -1;

			try {
				serviceId = request.getIntegerParameter("serviceId").intValue();
				instanceId = request.getIntegerParameter("instanceId")
						.intValue();
			} catch (Exception e) {
			}

			if (securityManager.checkPermission(commandId, serviceId,
					instanceId, participant, persistence)) {
				this.executeDynamicCommand(persistence, request);
				this.pcp.addDatabaseConnection(persistence);
			} else {
				ArCoLIVELogRepository.getInstance().log(
						"Permission denied for command " + commandId
								+ " from the participant " + participant);
				(new SendDeniedPermissionCommandCommand(request)).execute();
			}

		} catch (ArCoLIVEPersistenceException alpe) {
			// TEM QUE TRATAR OS Erros de acesso ao banco
			(new SendDatabaseErrorMessageCommand(request)).executeNoException();
			if ((this.pcp != null) && (persistence != null)) {
				this.pcp.addDatabaseConnection(persistence);
			}

		} catch (ArCoLIVECommandNotFoundException alcnfe) {
			ArCoLIVELogRepository.getInstance().log(alcnfe.getMessage());

		} catch (ArCoLIVECannotProcessCommandException alcpce) {
			if ((this.pcp != null) && (persistence != null)) {
				this.pcp.addDatabaseConnection(persistence);
			}
			ArCoLIVELogRepository.getInstance().log(
					"Error from participant " + participant + ": "
							+ alcpce.getMessage());
			(new SendInvalidCommandCommand(participant, event
					.getStringRequest())).execute();
		}

	}

	public ArCoLIVEParticipantGroup getParticipantGroup() {
		return participantGroup;
	}

	public static void main(String[] args) {

	}

}
